3.06. Справочник по Cypher
Разработчику
Аналитику
Тестировщику
Архитектору
Инженеру
Справочник по Cypher
1. Общая структура запроса
Cypher — декларативный язык запросов, вдохновлённый SQL и ASCII-графикой. Запрос строится из клауз, исполняемых в строго определённом порядке:
[USE database]
[MATCH ...]
[OPTIONAL MATCH ...]
[WHERE ...]
[WITH ...]
[UNWIND ...]
[CREATE ... | MERGE ...]
[SET ... | REMOVE ...]
[DELETE ... | DETACH DELETE ...]
[RETURN ...]
[ORDER BY ...]
[SKIP ...]
[LIMIT ...]
Клаузы не могут следовать в произвольном порядке. Допустимые порядки определяются синтаксической грамматикой Cypher (ISO/IEC 39075:2022 — GQL, в котором Cypher лег в основу).
Фазы выполнения запроса
- Определение контекста:
USE,CALL { ... }, подзапросы. - Чтение данных:
MATCH,OPTIONAL MATCH. - Фильтрация:
WHERE. - Группировка данных:
WITH,UNWIND. - Мутации:
CREATE,MERGE,SET,REMOVE,DELETE. - Проекция результата:
RETURN,ORDER BY,SKIP,LIMIT.
⚠️ Обратите внимание: в Cypher нет поддержки транзакций на уровне синтаксиса в виде
BEGIN/COMMIT. Транзакции управляются на уровне протокола (Bolt) или через:autoвcypher-shell, либо явно в драйверах.
2. Сущности графа и их свойства
2.1 Узлы (Nodes)
Синтаксис:
(n) — неназванный узел
(n:Label) — узел с меткой
(n:Label1:Label2) — узел с несколькими метками
(n {prop: value}) — узел со свойствами
Свойства узла:
- Любое количество пар «ключ–значение».
- Ключ — строка, идентификатор (например,
name,created_at). - Значение — литерал одного из поддерживаемых типов (см. раздел 4).
- Свойства неиндексируются по умолчанию. Индекс или ограничение (
CREATE INDEX,CREATE CONSTRAINT) создаётся явно и применяется только к свойствам конкретных меток.
Особенности:
- Узел может иметь ноль, одну или множество меток.
- Метки — это не типы, а теги; не гарантируют схемы, но позволяют фильтровать (
MATCH (n:User)). - Нельзя создать узел без меток через
CREATE— по крайней мере одна метка обязательна в Neo4j 5+ (в более ранних версиях допускались «анонимные» узлы, но они не рекомендовались).
2.2 Рёбра (Relationships)
Синтаксис:
-[r]- — ненаправленное, без типа
-[r:TYPE]- — ненаправленное, с типом
-[r:TYPE]-> — направленное (от узла слева к узлу справа)
<-[r:TYPE]- — направленное (в обратную сторону)
-[r {prop: v}]-> — с свойствами
Свойства ребра:
- Аналогично узлам: пары ключ–значение.
- Каждое ребро обязательно имеет ровно один тип (
TYPE). - Тип ребра не может быть параметром (нельзя писать
-[r:$type]->). - Ребро не может существовать без двух узлов-концов.
Особенности:
- Направление — логическая конструкция. Физически в Neo4j хранятся оба указателя (from→to, to←from), но семантика направления определяется в запросе.
- В Neo4j рёбра не могут иметь меток (в отличие, например, от ArangoDB).
- Нельзя создать ребро между одинаковыми узлами (саморебро, self-loop) без явного указания — но саморёбра разрешены и поддерживаются.
2.3 Паттерны (Patterns)
Цепочка узлов и рёбер в MATCH называется паттерном. Примеры:
(a)-[r]->(b) — одно ребро
(a)-[r1]->(b)<-[r2]-(c) — вилка
(a)-[*1..3]->(b) — переменная длина (от 1 до 3 рёбер)
(a)-[*]->(b) — любая длина ≥1 (осторожно: может быть очень тяжёлым)
(a)-[r:KNOWS*2]->(b) — ровно 2 ребра типа KNOWS
Сокращённые формы:
-->эквивалентно-[]->()-->()-->()— цепочка из трёх узлов и двух рёбер без именования
Для переменной длины:
*n— ровно n рёбер*n..m— от n до m (включительно)*n..— ≥n*..m— ≤m*— ≥1 (синоним*1..)
⚠️ Переменная длина без ограничений (
*) ведёт к экспоненциальному росту пути и крайне не рекомендуется в production безLIMIT,WHEREна свойствах или алгоритмических ограничений (например,shortestPath).
3. Клаузы и их параметры
3.1 MATCH
- Выполняет поиск по существующим данным.
- Если паттерн не найден — строка выпадает из потока (как
INNER JOIN). - Поддерживает:
- Именованные и анонимные узлы/рёбра.
- Переменную длину.
- Неоднородные типы:
(a)-[:FRIENDS|COLLEAGUE]->(b) - Ограничения в
WHERE, а не в самом паттерне (паттерн описывает топологию, фильтрация — отдельно).
Пример:
MATCH (u:User)-[r:SENT]->(m:Message)-[:TO]->(v:User)
WHERE u.active = true AND m.timestamp > datetime('2025-01-01')
RETURN u.name, v.name, count(m) AS messages
3.2 OPTIONAL MATCH
- Аналог
LEFT JOIN. - Если паттерн не найден — подставляются
nullдля всех связанных переменных. - Может быть несколько
OPTIONAL MATCHподряд.
3.3 WHERE
- Применяется после
MATCH, доWITH/RETURN. - Поддерживает логические операторы:
AND,OR,XOR,NOT. - Сравнения:
=,<>,<,<=,>,>=,IS NULL,IS NOT NULL. - Строковые операторы:
STARTS WITH,ENDS WITH,CONTAINS. - Регулярные выражения:
=~ 'regexp'(Java-совместимый синтаксис). - Проверка вхождения:
prop IN list,value IN [...],label IN labels(node). - Составные условия:
WHERE (n.age > 18 AND n.country = 'RU') OR (n.is_verified = true).
3.4 WITH
- Преобразует промежуточный поток строк.
- Позволяет:
- Агрегировать (
count,sum,collectи др.). - Фильтровать (
WHEREпослеWITH). - Переименовывать переменные.
- Передавать только часть данных в следующую фазу (экономия памяти).
- Агрегировать (
🔹 Важно: после
WITHнедоступны ранее объявленные переменные, если они не перечислены явно.
Пример:
MATCH (u:User)-[:POSTED]->(p:Post)
WITH u, count(p) AS post_count
WHERE post_count > 10
RETURN u.name, post_count
ORDER BY post_count DESC
3.5 UNWIND
- Разворачивает список в строки.
- Обратная операция к
collect().
UNWIND [1, 2, 3] AS x
RETURN x * 2 // → 2, 4, 6
Часто используется для импорта массивов свойств или подготовки данных для MERGE.
3.6 CREATE
- Создаёт новые узлы и рёбра.
- Не проверяет дубликаты — каждый вызов
CREATEпорождает новые сущности. - Рекомендуется использовать только при заведомо уникальных объектах.
3.7 MERGE
-
Поиск + условное создание.
-
Синтаксис:
MERGE (n:Label {key: value})
ON CREATE SET n.created = timestamp()
ON MATCH SET n.last_seen = timestamp() -
Проверяет весь паттерн целиком, а не по частям.
-
Может привести к созданию «фантомных» узлов при некорректной спецификации условий.
⚠️
MERGEна паттерне(a)-[r]->(b)без привязки к существующим узлам может создать два новых узла и ребро — даже еслиaиbдолжны были быть известны. Рекомендуется сначалаMATCH, затемMERGEс привязкой.
3.8 SET
- Назначает или обновляет свойства.
- Способы:
SET n.prop = value
SET n += {prop1: v1, prop2: v2} — объединение (merge, не полная замена)
SET n:Label — добавить метку
SET n:Label1:Label2 — несколько меток
🔸
SET n = {...}полностью заменяет все свойства — будьте осторожны.
3.9 REMOVE
- Удаляет свойства или метки:
REMOVE n.prop
REMOVE n:Label
3.10 DELETE и DETACH DELETE
DELETE r— удаляет только ребро.DELETE n— ошибка, если у узла остались рёбра (ограничение целостности).DETACH DELETE n— удаляет узел и все инцидентные рёбра.
4. Типы данных в Cypher
Cypher поддерживает следующие скалярные и составные типы (в Neo4j 5.x):
| Тип | Пример | Описание |
|---|---|---|
Null | null | Отсутствие значения. |
Boolean | true, false | Логический тип. |
Integer | 42, -1000 | 64-битное целое (long). |
Float | 3.14, -0.001e5 | 64-битное число с плавающей точкой (double). |
String | "hello" | UTF-8, неизменяемая строка. |
Date | date('2025-11-21') | Календарная дата (без времени). |
Time | time('14:30:00+03:00') | Время с временной зоной. |
LocalTime | localtime('14:30:00') | Время без зоны. |
DateTime | datetime('2025-11-21T14:30:00+03:00') | Дата + время + зона. |
LocalDateTime | localdatetime('2025-11-21T14:30:00') | Дата + время, без зоны. |
Duration | duration('P1Y2M3DT4H5M6.789S') | ISO 8601-совместимый интервал. |
Point | point({x: 1.0, y: 2.0}), point({latitude: 51.5, longitude: -0.12}) | 2D/3D точка; поддержка декартовых и географических координат. |
Составные типы:
List— упорядоченный массив значений:[1, "a", true]- Гетерогенный (разные типы допустимы).
- Индексация:
list[0],list[-1], срезыlist[1..3].
Map— именованный набор пар:{name: "Alice", age: 30}- Ключи — всегда строки, значения — любые типы.
- Доступ:
map.name,map['name'].
❗ Нет встроенных типов:
byte,short,BigDecimal,UUID,enum. Для UUID обычно используется строка ("550e8400-e29b-41d4-a716-446655440000").
5. Встроенные функции Cypher
Функции делятся на категории по назначению и поведению. Все функции чистые (не имеют побочных эффектов), за исключением генераторов случайных чисел и времён (rand(), timestamp()), которые считаются нестабильными.
5.1 Типизация и проверка
| Функция | Сигнатура | Описание |
|---|---|---|
coalesce(a, b, …) | coalesce(value1, value2, …) → value | Возвращает первый ненулевой аргумент. Аналог SQL COALESCE. |
exists(pattern) | exists((n)-[:R]->()) | Возвращает true, если заданный паттерн существует. Работает только в WHERE. Устаревшая (deprecated в Neo4j 5+), заменена на IS NOT NULL или COUNT { … } > 0. |
type(r) | type(relationship) → String | Возвращает тип ребра как строку. |
startNode(r), endNode(r) | startNode(r) → Node | Возвращает соответственно начальный и конечный узел ребра. |
id(n), id(r) | id(entity) → Integer | Возвращает внутренний идентификатор Neo4j (не стабилен, не рекомендуется к использованию в бизнес-логике). |
labels(n) | labels(node) → List<String> | Возвращает все метки узла в виде списка. Порядок не гарантирован. |
keys(map_or_node) | keys({a:1, b:2}) → ['a','b'] | Возвращает список ключей (имён свойств). |
5.2 Скалярные функции
| Функция | Описание |
|---|---|
size(list | string | pattern) | Длина списка ([1,2,3] → 3), строки ("abc" → 3) или кол-во рёбер в пути (size((a)-[*]->(b))). Для путей с переменной длиной — неэффективна. |
length(path) | Аналог size(path), но только для путей. MATCH p = (a)-[*]->(b) RETURN length(p) |
timestamp() | Текущее время в миллисекундах с Unix-эпохи (1732212000000). Неустойчивая функция. |
rand() | Случайное число ∈ [0.0, 1.0). Неустойчивая. |
toBoolean(x) | Преобразует в Boolean: строки "true"/"false" (регистронезависимо), 1/0, true/false. Иначе — null. |
toInteger(x), toFloat(x), toString(x) | Строгие преобразования. Ошибки → null. |
datetime(x), date(x), time(x) и аналоги | Конструкторы временных типов из строк, epoch-миллисекунд, map’ов. Пример: datetime({year:2025, month:11, day:21, hour:15}) |
5.3 Строковые функции
| Функция | Поведение |
|---|---|
trim(str), ltrim(str), rtrim(str) | Удаление пробелов (Unicode whitespace). |
toLower(str), toUpper(str) | Регистронезависимо (Unicode-aware). |
substring(str, start [, length]) | substring("abcdef", 1, 3) → "bcd" |
replace(str, old, new) | Замена всех вхождений. |
split(str, delimiter) | Возвращает список. split("a,b,c", ",") → ["a","b","c"] |
reverse(str) | reverse("abc") → "cba" |
left(str, n), right(str, n) | Первые/последние n символов. |
toStringOrNull(x) | Безопасное преобразование: возвращает null при невозможности, не падает. |
5.4 Списковые функции
| Функция | Пример |
|---|---|
range(start, end [, step]) | range(1, 5) → [1,2,3,4,5]; range(0, 10, 3) → [0,3,6,9] |
head(list), last(list) | Первый/последний элемент. При пустом списке — null. |
tail(list) | Все элементы, кроме первого. tail([1,2,3]) → [2,3] |
size(list) | Уже описано выше. |
reverse(list) | Обратный порядок. |
extract(item IN list | expr) | Устаревшая. Заменена на списковые comprehensions: [item IN list | expr] |
filter(item IN list WHERE pred) | [item IN list WHERE item > 0] |
[x IN list WHERE x > 0 | x * 2] | Комплексное выражение: фильтрация + преобразование. |
🔹 Важно: Cypher поддерживает list comprehensions и pattern comprehensions — мощные инструменты для inline-обработки коллекций.
Пример pattern comprehension:
MATCH (u:User)
RETURN u.name, [ (u)-[:FRIENDS]->(f) | f.name ] AS friends
Возвращает имя пользователя и список имён его друзей — даже если друзей нет (тогда [], не null).
5.5 Агрегатные функции
Выполняются автоматически при группировке (по неагрегированным полям в RETURN или WITH).
| Функция | Особенности |
|---|---|
count(*) | Считает строки (включая null). |
count(expr) | Считает не-null значения выражения. count(n) ≠ count(*), если n может быть null (например, в OPTIONAL MATCH). |
sum(x), avg(x), min(x), max(x) | Требуют числовых аргументов. При отсутствии строк — null, не 0. |
collect(x) | Возвращает список всех значений (включая null, если x может быть null). Порядок соответствует порядку строк в группе (но не гарантирован без ORDER BY). |
percentileCont(p), percentileDisc(p) | Непрерывный и дискретный перцентили. p ∈ [0.0, 1.0]. |
stDev(x), stDevP(x) | Стандартное отклонение выборки и генеральной совокупности. |
⚠️ Агрегация «съедает» переменные: после
WITH u, count(r) AS nпеременнаяrнедоступна.
5.6 Временные функции
| Функция | Пример |
|---|---|
datetime(), localdatetime() | Текущее время (с зоной / без). Неустойчивы. |
duration.between(start, end) | Разница между двумя датами/временами. Возвращает Duration. |
date.truncate('month', d) | Округление до начала месяца, квартала и т.п. Поддерживает: 'millennium', 'century', 'decade', 'year', 'quarter', 'month', 'week', 'day'. |
Пример:
WITH datetime('2025-11-21T15:30:00+03:00') AS now
RETURN date.truncate('week', now) // → 2025-11-17T00:00:00+03:00
5.7 Пространственные функции
| Функция | Описание |
|---|---|
point({x: …, y: …}) | 2D декартова точка. |
point({latitude: …, longitude: …}) | Географическая (WGS-84). Автоматически 2D/3D при height. |
distance(p1, p2) | Возвращает расстояние в метрах (географическая) или единицах координат (декартова). Работает только между точками одного типа. |
point.withinBBox(point, min, max) | Neo4j 5.21+, проверяет попадание в прямоугольник (bounding box). |
point.withinCircle(center, radius) | Нет стандартной функции — эмулируется через distance(p, center) <= radius. |
5.8 Функции APOC (не встроенные, но широко используемые)
Модуль APOC (Awesome Procedures on Cypher) — не часть ядра, но стандарт де-факто. Основные полезные функции:
| Функция | Назначение |
|---|---|
apoc.text.slug(str) | Преобразует в URL-friendly строку ("Привет, мир!" → "privet-mir"). |
apoc.date.convertFormat(str, fromFormat, toFormat) | Конвертация дат между форматами (Java SimpleDateFormat). |
apoc.coll.sort(list), apoc.coll.sortNodes(list, 'prop') | Сортировка списков и узлов. |
apoc.map.fromPairs([['a',1],['b',2]]) | Строит map из списка пар. |
apoc.create.uuid() | Генерирует UUID v4 (возвращает строку). |
apoc.cypher.run(query, params) | Динамический запрос внутри запроса (опасно, но мощно). |
⚠️ Использование APOC снижает переносимость запроса. Указывайте его явно как зависимость.
6. Параметры запросов
Cypher поддерживает параметризованные запросы — обязательная практика для безопасности и производительности.
Синтаксис
MATCH (n:User {name: $name})
WHERE n.age > $minAge
RETURN n
Параметры передаются отдельно (в драйверах — как map/dict):
{
"name": "Алексей",
"minAge": 18
}
Типы параметров
- Поддерживаются все скалярные типы (включая
Date,Pointкак объекты). - Поддерживаются списки и map’ы.
- Нельзя параметризовать:
- Имена меток (
: $label— запрещено) - Типы рёбер (
-[r:$type]->— запрещено) - Имена свойств в литералах (
n.$prop = …— запрещено; но можноn[$prop] = …) - Ключевые слова (
$clauseвRETURNи т.д.)
- Имена меток (
✅ Разрешено динамическое имя свойства:
MATCH (n)
WHERE n[$propName] = $value
RETURN n
7. Индексы и ограничения в контексте Cypher
Индексы и ограничения не влияют на синтаксис Cypher, но критически влияют на план выполнения.
Типы индексов (Neo4j 5+)
| Тип | Синтаксис создания | Когда используется |
|---|---|---|
| B-tree (по умолчанию) | CREATE INDEX user_name FOR (n:User) ON (n.name) | Равенство, диапазоны, сортировка. |
| Full-text | CREATE FULLTEXT INDEX post_title_desc FOR (n:Post) ON EACH [n.title, n.description] | Поиск по подстроке, CONTAINS, STARTS WITH (для текста), =~ (неоптимально). |
| Point (пространственный) | CREATE POINT INDEX geo_loc FOR (n:Place) ON (n.location) | distance(), WITHIN BBOX, фильтры по координатам. |
Text (над TEXT-свойствами) | Устаревший (до Neo4j 3.5), заменён full-text. |
Ограничения
| Тип | Синтаксис | Эффект |
|---|---|---|
UNIQUE | CREATE CONSTRAINT user_email FOR (n:User) REQUIRE n.email IS UNIQUE | Запрещает дубликаты; автоматически создаёт уникальный B-tree индекс. |
NODE KEY | CREATE CONSTRAINT user_ext_id FOR (n:User) REQUIRE (n.system, n.external_id) IS NODE KEY | Составной уникальный ключ (несколько свойств). |
PROPERTY EXISTENCE | CREATE CONSTRAINT user_name_req FOR (n:User) REQUIRE n.name IS NOT NULL | Запрещает null в свойстве (только в Enterprise Edition). |
🔹 При
MERGE (n:User {email: $e})наличиеUNIQUEна
8. Производительность: EXPLAIN, PROFILE, планы
8.1 Управление выполнением
EXPLAIN …— показывает предполагаемый план без выполнения.PROFILE …— выполняет запрос и показывает фактический план + статистику (строки, итерации, время).
8.2 Ключевые операторы в плане
| Оператор | Значение |
|---|---|
NodeByLabelScan | Полный проход по всем узлам метки (плохо без WHERE). |
NodeIndexSeek | Поиск по индексу (хорошо). |
NodeIndexScan | Сканирование индекса (например, при CONTAINS без full-text). Медленнее Seek. |
Expand(All) | Обход рёбер. Сложность ~ степень узла × количество узлов. |
VarLengthExpand | Переменная длина — потенциально экспоненциален. |
Projection, Filter, Sort, Eager | Стандартные операции. |
Eager | Красный флаг: заставляет буферизовать все данные → OOM при больших объёмах. Возникает при смешивании чтения и записи в одном запросе без WITH. |
8.3 Анти-паттерны
| Анти-паттерн | Проблема | Решение |
|---|---|---|
MATCH (n) WHERE n.prop CONTAINS 'text' без full-text | Полный скан + фильтр | Создать full-text индекс. |
MATCH (a)-[*]->(b) без ограничений | Экспоненциальный взрыв путей | Использовать shortestPath, ограничить длину, добавить WHERE на свойства. |
MERGE (a:Label1 {id: x}) MERGE (b:Label2 {id: y}) MERGE (a)-[:R]->(b) | Может создать a и b дважды (гонка) | Сначала MERGE узлы отдельно, затем MERGE ребро. Или использовать apoc.merge.node. |
WITH * перед агрегацией | Передаёт ненужные переменные → рост памяти | Перечислять только нужное. |
Длинная цепочка MATCH → CREATE → MATCH → CREATE | Нет изоляции → Eager | Разбивать на подзапросы через CALL { … }. |
9. Совместимость с GQL (ISO/IEC 39075:2022)
Cypher стал основой для Graph Query Language (GQL) — первого стандарта запросов к графовым БД (утверждён ISO в 2022 г.).
Что перешло в GQL:
- Синтаксис
MATCH,WHERE,RETURN. - Паттерны с
()-->(), переменной длиной[*n..m]. - Использование
:для меток/типов. WITH,UNWIND,CREATE,MERGE(с оговорками).- Типы данных (включая временные и пространственные).
Что не вошло или изменено:
REMOVE,DETACH DELETE— заменены наSET … = NULLи явное управление ссылками.OPTIONAL MATCH→LEFT MATCH(в GQL).MERGE— нет в GQL; предлагается использовать комбинациюMATCH+INSERT.- Параметры —
$name, но синтаксис может варьироваться. - Функции — только стандартный набор;
apoc.*иid()исключены.
🔹 Neo4j 5.21+ частично поддерживает подмножество GQL (через флаг
cypher.gql_compat=true). Memgraph и Oracle PGQL ближе к GQL.
10. Практические рекомендации для документации и преподавания
- Избегайте упоминания
id()— это внутренняя деталь реализации. - Всегда используйте параметры — даже в учебных примерах.
- Демонстрируйте
EXPLAIN— чтобы учащиеся видели разницу междуScanиSeek. - Покажите разницу
COUNT(*)vsCOUNT(n)— частая ошибка новичков. - Объясняйте «жадность»
MERGEна живых примерах (включая создание фантомных узлов). - Вводите pattern comprehensions рано — они делают запросы компактнее и чище.
11. Грамматика Cypher (упрощённая EBNF)
Для целей документации «Вселенной IT» приводим формализованную, но читаемую структуру языка. Основана на Cypher Grammar (OpenCypher), адаптирована под Neo4j 5.x.
11.1 Базовые терминалы
Identifier = [a-zA-Z_][a-zA-Z0-9_]* ;
SymbolicName = Identifier ; // имя переменной: n, rel, user
LabelName = ":" Identifier ; // :User, :Product
RelTypeName = ":" Identifier ; // :FRIENDS, :PURCHASED
PropertyKeyName = Identifier ; // name, created_at
StringLiteral = '"' ( ~["\\] | '\\' ["\\/bfnrt] | '\\' 'u' [0-9a-fA-F]{4} )* '"' ;
IntegerLiteral = [0-9]+ ;
FloatLiteral = [0-9]+ "." [0-9]* ( [eE] [+-]? [0-9]+ )? ;
BooleanLiteral = "true" | "false" ;
NullLiteral = "null" ;
Parameter = "$" Identifier ;
11.2 Узел и ребро
NodePattern = "(" [SymbolicName] [LabelName {LabelName}] [Properties]? ")" ;
RelationshipPattern
= "-[" [SymbolicName] [RelTypes]? [Range]? [Properties]? "]-" ;
| "-[" [SymbolicName] [RelTypes]? [Range]? [Properties]? "]->" ;
| "<-[" [SymbolicName] [RelTypes]? [Range]? [Properties]? "]-" ;
RelTypes = RelTypeName { "|" RelTypeName } ;
Range = "*" [IntegerLiteral] [".." [IntegerLiteral]] ;
Properties = "{" [PropertyExpression {"," PropertyExpression}] "}" ;
PropertyExpression= PropertyKeyName ":" Expression ;
Пример соответствия:
(n:User:Admin {name: "Тимур", active: true})
-[r:AUTHORED|MODIFIED*1..3 {at: $ts}]→
(m:Article)
11.3 Паттерны и запрос
Pattern = NodePattern { RelationshipPattern NodePattern } ;
Query = [UseClause]
{ ReadClause | WithClause | UnwindClause }
{ UpdateClause }
ReturnClause? ;
ReadClause = "MATCH" Pattern { "," Pattern }
| "OPTIONAL MATCH" Pattern { "," Pattern } ;
WithClause = "WITH" [Distinct]? ReturnBody ;
UnwindClause = "UNWIND" Expression "AS" SymbolicName ;
UpdateClause = CreateClause
| MergeClause
| SetClause
| RemoveClause
| DeleteClause ;
CreateClause = "CREATE" Pattern { "," Pattern } ;
MergeClause = "MERGE" Pattern
[ "ON CREATE" SetClause ]
[ "ON MATCH" SetClause ] ;
SetClause = "SET" ( SetItem { "," SetItem } ) ;
SetItem = SymbolicName "." PropertyKeyName "=" Expression
| SymbolicName "+=" MapLiteral
| SymbolicName LabelName {LabelName} ;
RemoveClause = "REMOVE" ( RemoveItem { "," RemoveItem } ) ;
RemoveItem = SymbolicName "." PropertyKeyName
| SymbolicName LabelName ;
DeleteClause = ("DELETE" | "DETACH DELETE") Expression { "," Expression } ;
ReturnClause = "RETURN" [Distinct]? ReturnBody ;
ReturnBody = Projection { "," Projection }
[ OrderBy? ]
[ Skip? ]
[ Limit? ] ;
Projection = Expression [ "AS" SymbolicName ] ;
Expression = OrExpression ;
OrExpression = XorExpression { "OR" XorExpression } ;
XorExpression = AndExpression { "XOR" AndExpression } ;
AndExpression = NotExpression { "AND" NotExpression } ;
NotExpression = "NOT" NotExpression | Comparison ;
Comparison = AddOrSubtract
[ ("=" | "<>" | "<" | "<=" | ">" | ">=" | "IS" ["NOT"] "NULL"
| "CONTAINS" | "STARTS WITH" | "ENDS WITH"
| "=~") AddOrSubtract ] ;
🔹 Полная грамматика содержит ~200 правил; здесь приведено ядро, достаточное для понимания структуры и автоматизированной проверки.
12. Сравнение с другими графовыми языками
| Критерий | Cypher | PGQL (Oracle) | Gremlin | GQL (ISO) |
|---|---|---|---|---|
| Парадигма | Декларативный | Декларативный | Императивный/функциональный | Декларативный |
| Синтаксис | ASCII-art графы | SQL-подобный | Цепочки вызовов (g.V().out()…) | Основан на Cypher |
MATCH | MATCH (a)-[:KNOWS]->(b) | SELECT a, b FROM MATCH (a)-[e:KNOWS]->(b) | g.V().hasLabel('A').out('KNOWS').as('b') | (a)-[:KNOWS]->(b) |
| Переменная длина | [*1..3] | {1,3} | repeat(out()).times(3) | [*1..3] |
| Агрегация | RETURN count(*) | GROUP BY … SELECT COUNT(*) | group().by(count()) | RETURN count(*) |
| Подзапросы | CALL { … } | Подзапросы в FROM | Вложенные цепочки, sideEffect() | Поддержка через LET и CALL |
| Параметры | $param | :param (bind variables) | Переменные замыкания (в Groovy/Java) | $param |
| Производительность | Оптимизируется до Pipe-планов | CBO на основе статистик Oracle | JIT-компиляция в Traversal VM | Стандартизированный CBO |
| Поддержка типов | Слабая (динамическая схема) | Интеграция с типами SQL | Нет типов (все — Object) | Типизация через профили |
🔸 Cypher vs Gremlin:
- Cypher лучше для аналитических и поисковых задач (например, «найти друзей друзей»).
- Gremlin эффективнее для процедурных и итеративных алгоритмов (PageRank, SSSP, LPA), особенно при глубокой вложенности.
🔸 PGQL ближе к SQL — легко осваивается DBA Oracle, но менее выразителен для сложных графовых топологий.
13. Сложные сценарии: примеры с пояснением
13.1 Рекомендательная система (Collaborative Filtering)
Задача: «Найти товары, купленные друзьями пользователя, но не купленные им самим».
MATCH (u:User {id: $user_id})
MATCH (u)-[:FRIENDS*1..2]-(f:User)-[:BOUGHT]->(p:Product)
WHERE NOT (u)-[:BOUGHT]->(p)
WITH p, count(*) AS score
ORDER BY score DESC
LIMIT 10
RETURN p.name, p.category, score
⚠️ Проблемы:
FRIENDS*1..2может создать дубликаты (если два пути до одного друга).
Решение:WITH DISTINCT fпосле первогоMATCH.NOT (u)-[:BOUGHT]->(p)— может быть медленным без индекса на(u)-[:BOUGHT]->(p).
Решение: Предварительно собратьCOLLECT(id(p))купленных товаров → фильтровать черезWHERE NOT id(p) IN bought_ids.
13.2 Обнаружение циклов (например, саморекурсивные зависимости)
MATCH p = (n)-[:DEPENDS_ON*2..]->(n)
WHERE length(p) = size(nodes(p)) // исключить повторяющиеся узлы (простой цикл)
RETURN nodes(p) AS cycle, length(p) AS depth
LIMIT 5
🔹
*2..— минимальная длина цикла = 2 (ребро → узел → обратно).
🔹 Условиеlength(p) = size(nodes(p))гарантирует, что все узлы уникальны (простой цикл).
❗ БезLIMITи ограничения длины запрос может не завершиться.
Альтернатива — использование shortestPath для проверки существования цикла:
MATCH (n)
WHERE shortestPath((n)-[:DEPENDS_ON*1..]->(n)) IS NOT NULL
RETURN n
LIMIT 10
13.3 Выявление сообществ (Community Detection через Label Propagation — упрощённо)
Neo4j Graph Data Science Library (GDS) рекомендуется для production, но можно эмулировать:
// Шаг 1: инициализация — каждому узлу — свой "label"
MATCH (n:User)
SET n.temp_label = id(n)
// Шаг 2: итеративно обновлять метку → мода соседей
// (выполняется N раз вручную или через APOC)
CALL apoc.periodic.iterate(
"MATCH (n:User) RETURN n",
"MATCH (n)-[:FRIENDS]-(f)
WITH n, apoc.coll.frequencies([f IN collect(f) | f.temp_label]) AS freqs
WITH n, reduce(m = {label: null, count: 0}, item IN freqs |
CASE WHEN item.count > m.count THEN {label: item.item, count: item.count} ELSE m END
).label AS new_label
SET n.temp_label = new_label",
{batchSize: 1000, parallel: true}
)
YIELD batches, total
RETURN batches, total
🔸 Это учебная реализация. В реальности — используйте
gds.labelPropagation.stream().
14. Подзапросы и композиция
14.1 CALL { … } — изолированный подзапрос
MATCH (u:User {active: true})
CALL {
WITH u
MATCH (u)-[:POSTED]->(p:Post)
WHERE p.created > datetime() - duration('P7D')
RETURN count(p) AS recent_posts
}
RETURN u.name, recent_posts
Особенности:
WITH u— передача контекста.- Переменные извне (
u) доступны только в первомWITHподзапроса. - Переменные внутри (
recent_posts) доступны снаружи. - Подзапрос выполняется на каждой строке внешнего потока.
14.2 UNION и UNION ALL
MATCH (u:User)-[:MODERATOR_OF]->(g:Group)
RETURN u.name AS principal, g.name AS target, 'moderator' AS role
UNION
MATCH (u:User)-[:MEMBER_OF]->(g:Group)
RETURN u.name, g.name, 'member'
UNION— удаляет дубликаты (требует совпадения имён/типов колонок).UNION ALL— сохраняет дубликаты, быстрее.
❗ Нельзя использовать
UNIONмеждуREADиWRITE— только в чисто-RETURN-части.
15. Особенности реализаций
15.1 Neo4j 5.21+ (Enterprise/Community)
- Полная поддержка GQL подмножества (через
cypher.gql_compat=true). - Composite indexes — индексы по нескольким свойствам:
Используется при
CREATE INDEX user_status_created FOR (n:User) ON (n.status, n.created_at)WHERE n.status = 'active' AND n.created_at > …(в порядке объявления). - Subquery predicates:
Замена устаревшему
WHERE EXISTS {
MATCH (n)-[:FRIENDS]->(f:User)
WHERE f.trust_score > 0.8
}exists((n)-[:FRIENDS]->(:User {trust_score: …})).
15.2 Apache AGE (PostgreSQL extension)
- Реализует ~80% Cypher-синтаксиса.
- Нет поддержки:
MERGE(толькоCREATE+ ручная проверка),REMOVE,- полных временных типов (
datetime→timestamptz), apoc.*.
- Работает поверх PostgreSQL, использует его индексы (
GIN,GiST) и параллелизм. - Пример запроса:
SELECT * FROM cypher('graph_name', $$
MATCH (u:User {status: 'active'})
RETURN u.name, u.props->>'email'
$$) AS (name agtype, email agtype);
🔹 AGE — хороший выбор, если уже есть PostgreSQL и нужна гибридная (реляционная + графовая) модель.